/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package bmodeling;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * @author M. Hautle
 */
public class Loader2 extends ClassLoader {
	// FIXME class path im manifest noch parsen ....
	/** The byte count to read at once when fetching the content of a class file. */
	private static final int READ_BLOCK_SIZE = 1024;

	/** The byte count to read at once when fetching the content of a class file. */
	private static final int BUFFER_BLOCK_SIZE = 4 * READ_BLOCK_SIZE;

	/** The Openoffice lib names. */
	public static final String[] OOO_LIBS = { "unoil.jar", "juh.jar", "jurt.jar", "ridl.jar" };

	/** The root node of the lookup tree. */
	private final Node lookupRoot = new Node();

	/** Shared Transfer Object for {@link #findClass(String)} (cause this call is Thread safe) */
	private final Entry classEntry = new Entry();

	/**
	 * Default constructor
	 * 
	 * @param libPath The library path
	 * @param oooPath The openoffice library path
	 * @throws IOException
	 */
	public Loader2(String libPath, String oooPath) throws IOException {
		addLibs(libPath);
		addOOOLibs(oooPath);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		InputStream in = null;
		try {
			final Entry e = getEntry(name.replace('.', '/') + ".class", classEntry);
			if (e == null)
				throw new ClassNotFoundException(name);
			in = e.parent.jar.getInputStream(e.entry);
			byte[] data = new byte[BUFFER_BLOCK_SIZE];
			int i = 0;
			for (int cnt = 0; (cnt = in.read(data, i, READ_BLOCK_SIZE)) != -1;) {
				i += cnt;
				// enlarge the buffer if necessary
				if (data.length - READ_BLOCK_SIZE < i) {
					final byte[] old = data;
					data = new byte[old.length + BUFFER_BLOCK_SIZE];
					System.arraycopy(old, 0, data, 0, old.length);
				}
			}
			return defineClass(name, data, 0, i);
		} catch (IOException e) {
			throw new ClassNotFoundException(name, e);
		} finally {
			if (in != null)
				try {
					in.close();
				} catch (IOException e) {
					throw new ClassNotFoundException(name, e);
				}
		}
	}

	/**
	 * @param name A file name (with / as package seperator)
	 * @param entry An {@link Entry} object where to put the result
	 * @return The passed entry object containing the resolved entry or null if nothing was found
	 */
	private Entry getEntry(String name, Entry entry) {
		return lookupRoot.lookup(name, entry);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected URL findResource(String name) {
		try {
			final Entry e = lookupRoot.lookup(name, new Entry());
			if (e != null)
				return e.parent.getEntryUrl(name);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected Enumeration<URL> findResources(String name) throws IOException {
		final ArrayList<URL> res = lookupRoot.findResources(name);
		// return a corresponding enumeration
		return new Enumeration<URL>() {
			private final Iterator<URL> i = res.iterator();

			@Override
			public boolean hasMoreElements() {
				return i.hasNext();
			}

			@Override
			public URL nextElement() {
				return i.next();
			}
		};
	}

	/**
	 * Adds all libraries in the given path to the class path
	 * 
	 * @param libPath The library path
	 * @throws IOException
	 */
	private void addLibs(String libPath) throws IOException {
		final File[] libs = new File(libPath).listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".jar");
			}
		});
		for (File f : libs)
			lookupRoot.addJar(new Jar(f));
	}

	/**
	 * @param oooPath The openoffice path
	 * @throws IOException
	 */
	private void addOOOLibs(String oooPath) throws IOException {
		oooPath = getOOOLibDir(oooPath);
		for (String lib : OOO_LIBS)
			lookupRoot.addJar(new Jar(new File(oooPath + lib)));
	}

	/**
	 * Checks if the given folder contains the {@link #OOO_LIBS}.
	 * 
	 * @param dir The folder to check
	 * @return True if the given folder contains the libs
	 */
	public static boolean checkOOODir(String dir) {
		dir = getOOOLibDir(dir);
		for (String f : OOO_LIBS)
			if (!new File(dir + f).exists())
				return false;
		return true;
	}

	/**
	 * Returns the openoffice lib dir of an openoffice dir.
	 * 
	 * @param oooDir The openoffice dir
	 * @return The openoffice lib dir
	 */
	private static String getOOOLibDir(String oooDir) {
		return oooDir + File.separator + "program" + File.separator + "classes" + File.separator;
	}
}
